﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using MyUtils.Investigate;

namespace MyUtils
{
	public static class WeakEventHandler
	{
		// TODO T4 for Func Action and EventHandler

		public static WeakEventHandler<Action> Create(Action act)
		{
			return new WeakEventHandler<Action>(act);
		}

		public static WeakEventHandler<T> Create<T>(T act, Action<T> unsubscribe = null) where T : class
		{
			return new WeakEventHandler<T>((Delegate)(object)act, unsubscribe);
		}

		public static WeakEventHandler<Action<T>> Create<T>(Action<T> act)
		{
			return new WeakEventHandler<Action<T>>(act);
		}

		public static WeakEventHandler<Action<T1, T2>> Create<T1, T2>(Action<T1, T2> act)
		{
			return new WeakEventHandler<Action<T1, T2>>(act);
		}

	}

	public static class Weak
	{
		static Weak()
		{
			_genericSubscribe = typeof (Weak).GetMethods().Where(x => x.Name == "Subscribe").Single();
		}

		private static readonly MethodInfo _genericSubscribe;

		/// <summary>
		/// Subscribe weak event handler to event
		/// </summary>
		/// <typeparam name="TDelegate">Delegate or Event Type</typeparam>
		/// <param name="subscribing">action for subscribing: x => publisher.Event1 += x</param>
		/// <param name="unsubscribing">action for automatic unsubscribing (after reference is dead): x => publisher.Event1 -= x</param>
		/// <param name="handler">MyEventHandler</param>
		/// <returns>Weak Handler</returns>
		public static TDelegate Subscribe<TDelegate>(Action<TDelegate> subscribing, Action<TDelegate> unsubscribing, TDelegate handler) where TDelegate : class
		{
			var weakHandler = (TDelegate)new WeakEventHandler<TDelegate>((Delegate)(object)handler, unsubscribing);
			subscribing(weakHandler);
			return weakHandler;
		}

//		public static void MagicSubscribe(object publisher, string eventName, string callerMethodName)
//		{
//		}

//		public static void MagicSubscribe(object publisher, string eventName, Delegate del)
//		{
//		}

		public static object MagicSubscribe(Action subscribe)
		{
			using (P.S("WD_MS"))
			{
				if (subscribe == null)
				{
					throw new ArgumentNullException("subscribe");
				}
				if (GetSubscribersCount(subscribe) != 1)
				{
					throw new ArgumentException(string.Format("Single method inside subscribe delegate is required. There are {0} delegates instead.", GetSubscribersCount(subscribe)));
				}

				MonoReflectionInstruction[] instructions;

				using (P.S("WD_MS_decompile"))
				{
					instructions = subscribe.Method.GetInstructions().Where(x => x.OpCode != OpCodes.Nop).ToArray();
				}

				object publisher;
				Type publisherType;
				Type delegateType;
				MethodInfo handlerMethod;
				Delegate handler;
				MethodInfo subscriberAccessor;
				MethodInfo unsubscriberAccessor;
				Delegate subscribing;
				Delegate unsubscribing;

				using (P.S("WD_MS_mining"))
				{

					publisher = instructions.Select(x => x.Operand).OfType<FieldInfo>().First().GetValue(subscribe.Target);
					publisherType = publisher.GetType();
					subscriberAccessor = (MethodInfo)instructions.Where(x => x.OpCode == OpCodes.Callvirt).Single().Operand; // subscribing accesor

					var i = subscriberAccessor.Name.IndexOf('_');
					if (i < 0)
					{
						throw new Exception();
					}
					var eventName = subscriberAccessor.Name.Substring(i + 1);

					var eventInfo = publisher.GetType().GetEvent(eventName);
					subscriberAccessor = eventInfo.GetAddMethod();
					unsubscriberAccessor = eventInfo.GetRemoveMethod();

					// METHOD
					var ldftn_aka_methodInfo = instructions.Single(x => x.OpCode == OpCodes.Ldftn).Operand;
					handlerMethod = (MethodInfo)ldftn_aka_methodInfo;

					// TYPE
					var newobj_aka_delegateCtor = instructions.Single(x => x.OpCode == OpCodes.Newobj).Operand;
					delegateType = ((ConstructorInfo)newobj_aka_delegateCtor).DeclaringType;

					var subscriber = subscribe.Target.GetType().Name.Contains("<")
					                 	? subscribe.Target.GetType().GetFields().Where(x => x.Name.EndsWith("_this")).Single().GetValue(subscribe.Target) // closure
					                 	: subscribe.Target; // instance method

					if (handlerMethod.DeclaringType != subscriber.GetType())
					{
						throw new Exception("Could not determine subscriber instance");
					}

					// handler
					handler = Delegate.CreateDelegate(delegateType, subscriber, handlerMethod);

					var subUnsubDelegateType = typeof(Action<>).MakeGenericType(delegateType);


					// MAKE subscriber-unsubscriber dynamic closure class
					//var closureType = DynamicClassFactory.CachedCreateSubscriberActionClosureClass(delegateType, publisherType, subscriberAccessor, unsubscriberAccessor);
					//var closureInstance = Activator.CreateInstance(closureType);
					//closureInstance.Reflect("_publisher", publisher);
					using (P.S("WD_MS_mining_dynamic_methods"))
					{
						// todo use event publisher as a closure type, and create synamic method for specified closure type. This way allows dismiss an instance of a closure type and make better caching
						var closureType = /*publisherType;*/ typeof(Box<>).MakeGenericType(publisherType);
						var closureInstance = /*publisher;*/ Activator.CreateInstance(closureType, publisher);

						DynamicMethod dm1, dm2;

						using (P.S("WD_MS_mining_dynamic_methods_create_dynmeth"))
						{
							dm1 = new DynamicMethod("", typeof(void), new[] {closureType, delegateType}, closureType, true);
							var il = dm1.GetILGenerator();
							il.Emit(OpCodes.Ldarg_0);
							il.Emit(OpCodes.Ldfld, closureType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Single());
							il.Emit(OpCodes.Ldarg_1);
							il.Emit(subscriberAccessor.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, subscriberAccessor);
							il.Emit(OpCodes.Ret);

							dm2 = new DynamicMethod("", typeof(void), new[] {closureType, delegateType}, closureType, true);
							il = dm2.GetILGenerator();
							il.Emit(OpCodes.Ldarg_0);
							il.Emit(OpCodes.Ldfld, closureType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Single());
							il.Emit(OpCodes.Ldarg_1);
							il.Emit(unsubscriberAccessor.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, unsubscriberAccessor);
							il.Emit(OpCodes.Ret);
						}

						using (P.S("WD_MS_mining_dynamic_methods_create_delegate"))
						{
							subscribing = dm1.CreateDelegate(subUnsubDelegateType, closureInstance);
							unsubscribing = dm2.CreateDelegate(subUnsubDelegateType, closureInstance);
						}
					}
				}

				using (P.S("WD_MS_real"))
				{
					var realSubscribe = _genericSubscribe.MakeGenericMethod(delegateType);

					using (P.S("WD_MS_real_invoke"))
					{
						// var act = typeof(Action<,,>).MakeGenericType(subscribing.GetType(), unsubscribing.GetType(), handler.GetType());
						// DynamicInvokeOptimizer.Compile(realSubscribe, act);
						return realSubscribe.Invoke(null, new object[] {subscribing, unsubscribing, handler});
					}
				}
			}
		}

		public static int GetSubscribersCount(Delegate del)
		{
			return del == null ? 0 : del.GetInvocationList().Length;
		}
	}

	public class WeakEventHandler<TDelegate> where TDelegate : class
	{
		readonly Action<TDelegate> _unsubscrube;
		readonly TDelegate _handler;

		public MethodInfo MethodInfo { get; private set; }

		public WeakEventHandler(Delegate eventHandler, Action<TDelegate> unsubscribe = null)
		{
			_unsubscrube = unsubscribe;
			if (!typeof(Delegate).IsAssignableFrom(typeof(TDelegate)))
			{
				throw new ArgumentException("TDelegate generic parameter should be Delegate", "");
			}

			if (eventHandler == null)
			{
				throw new ArgumentNullException("eventHandler");
			}

			if (!typeof(TDelegate).IsAssignableFrom(eventHandler.GetType()))
			{
				throw new ArgumentException("eventHandler should be of type " + typeof(TDelegate), "eventHandler");
			}

			MethodInfo = eventHandler.Method;
			_handler = DynamicClassFactory.GetWeakDelegate<TDelegate>(eventHandler, unsubscribe);
		}

		public TDelegate Handler
		{
			get { return _handler; }
		}

		public static implicit operator TDelegate(WeakEventHandler<TDelegate> weh)
		{
			return weh.Handler;
		}
	}
}
